#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>

cv::Mat my_hit_or_miss(const cv::Mat& matPadded, const cv::Mat& kernel) {
    CV_Assert(matPadded.type() == CV_8UC1);
    CV_Assert(kernel.type() == CV_32SC1);
    
    int rows = matPadded.rows - kernel.rows + 1;
    int cols = matPadded.cols - kernel.cols + 1;
    cv::Mat mat = cv::Mat::zeros(rows, cols, CV_8UC1);
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            bool isGood = true;
            cv::Mat roi(matPadded, cv::Rect(j, i, kernel.cols, kernel.rows));
            for (int ii = 0; ii < kernel.rows; ii++) {
                uchar* p = roi.ptr<uchar>(ii);
                const int* pp = kernel.ptr<int>(ii);
                for (int jj = 0; jj < kernel.cols; jj++) {
                    if ((p[jj] == 0 && pp[jj] == 1) || (p[jj] == 255 && pp[jj] == -1)) {
                        isGood = false;
                        break;
                    }
                }
                if (!isGood) {
                    break;
                }
            }
            mat.ptr<uchar>(i)[j] = (isGood ? 255 : 0);
        }
    }
    return mat;
}

int main(int argc, char **argv) {
    /*cv::Mat img = cv::imread("/home/xlll/Downloads/opencv/samples/data/lena.jpg", cv::IMREAD_GRAYSCALE);
    if (img.empty()) {
        std::cout << "Failed to read image!" << std::endl;
        return EXIT_FAILURE;
    }
    cv::imshow("source", img);
    
    cv::Mat stru = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(7, 8), cv::Point(-1, -1));
    cv::Mat matErode, matDilate;
    cv::morphologyEx(img, matErode, cv::MORPH_ERODE, stru, cv::Point(-1, -1), 1, cv::BORDER_REFLECT101);
    cv::morphologyEx(img, matDilate, cv::MORPH_DILATE, stru, cv::Point(-1, -1), 1, cv::BORDER_REFLECT101);
    */
    // open = dilate(erode)
    /*cv::Mat matOpen1, matOpen2;
    cv::morphologyEx(img, matOpen1, cv::MORPH_OPEN, stru, cv::Point(-1, -1), 1, cv::BORDER_REFLECT101);
    cv::morphologyEx(matErode, matOpen2, cv::MORPH_DILATE, stru, cv::Point(-1, -1), 1, cv::BORDER_REFLECT101);
    cv::Mat matOpenDiff = matOpen1 - matOpen2;
    std::vector<cv::Point> vOpenNonZeros;
    cv::findNonZero(matOpenDiff, vOpenNonZeros);
    std::cout << "vOpenNonZeros = " << vOpenNonZeros << std::endl;
    cv::imshow("matOpen1", matOpen1);
    cv::imshow("matOpen2", matOpen2);*/
    
    // close = erode(dilate)
    /*cv::Mat matClose1, matClose2;
    cv::morphologyEx(img, matClose1, cv::MORPH_CLOSE, stru, cv::Point(-1, -1), 1, cv::BORDER_REFLECT101);
    cv::morphologyEx(matDilate, matClose2, cv::MORPH_ERODE, stru, cv::Point(-1, -1), 1, cv::BORDER_REFLECT101);
    cv::Mat matCloseDiff = matClose1 - matClose2;
    std::vector<cv::Point> vCloseNonZeros;
    cv::findNonZero(matCloseDiff, vCloseNonZeros);
    std::cout << "vCloseNonZeros = " << vCloseNonZeros << std::endl;
    cv::imshow("matClose1", matClose1);
    cv::imshow("matClose2", matClose2);*/
    
    // gradient = dilate - erode
    /*cv::Mat matGradient1, matGradient2;
    cv::morphologyEx(img, matGradient1, cv::MORPH_GRADIENT, stru, cv::Point(-1, -1), 1, cv::BORDER_REFLECT101);
    matGradient2 = matDilate - matErode;
    cv::Mat matGradientDiff = matGradient1 - matGradient2;
    std::vector<cv::Point> vGradientNonZeros;
    cv::findNonZero(matGradientDiff, vGradientNonZeros);
    std::cout << "vGradientNonZeros = " << vGradientNonZeros << std::endl;
    cv::imshow("matGradient1", matGradient1);
    cv::imshow("matGradient2", matGradient2);*/
    
    // top hat = src - open
    /*cv::Mat matTopHat1, matTopHat2, matOpen;
    cv::morphologyEx(img, matTopHat1, cv::MORPH_TOPHAT, stru, cv::Point(-1, -1), 1, cv::BORDER_REFLECT101);
    cv::morphologyEx(img, matOpen, cv::MORPH_OPEN, stru, cv::Point(-1, -1), 1, cv::BORDER_REFLECT101);
    matTopHat2 = img - matOpen;
    cv::Mat matTopHatDiff = matTopHat1 - matTopHat2;
    std::vector<cv::Point> vTopHatNonZeros;
    cv::findNonZero(matTopHatDiff, vTopHatNonZeros);
    std::cout << "vTopHatNonZeros = " << vTopHatNonZeros << std::endl;
    cv::imshow("matTopHat1", matTopHat1);
    cv::imshow("matTopHat2", matTopHat2);*/
    
    // black hat = close - src
    /*cv::Mat matBlackHat1, matBlackHat2, matClose;
    cv::morphologyEx(img, matBlackHat1, cv::MORPH_BLACKHAT, stru, cv::Point(-1, -1), 1, cv::BORDER_REFLECT101);
    cv::morphologyEx(img, matClose, cv::MORPH_CLOSE, stru, cv::Point(-1, -1), 1, cv::BORDER_REFLECT101);
    matBlackHat2 = matClose - img;
    cv::Mat matBlackHatDiff = matBlackHat1 - matBlackHat2;
    std::vector<cv::Point> vBlackHatNonZeros;
    cv::findNonZero(matBlackHatDiff, vBlackHatNonZeros);
    std::cout << "vBlackHatNonZeros = " << vBlackHatNonZeros << std::endl;
    cv::imshow("matBlackHat1", matBlackHat1);
    cv::imshow("matBlackHat2", matBlackHat2);
    */
    
    /*
    cv::Mat mat(16, 16, CV_8UC1);
    cv::randu(mat, cv::Scalar(0), cv::Scalar(255));
    cv::Mat bin;
    cv::threshold(mat, bin, 128, 255, cv::THRESH_BINARY);
    std::cout << "bin = \n" << bin << std::endl << std::endl;
    
    cv::Mat kernel = (cv::Mat_<int>(3, 3) << 0, 1, 0, 1, -1, 1, 0, 1, 0);
    cv::Mat hitOrMiss;
    cv::morphologyEx(bin, hitOrMiss, cv::MORPH_HITMISS, kernel, cv::Point(-1, -1), 1, cv::BORDER_REFLECT101);
    std::cout << "hitOrMiss = \n" << hitOrMiss << std::endl << std::endl;
    
    cv::Mat binPadded;
    int top = kernel.rows / 2;
    int left = kernel.cols / 2;
    int bottom = kernel.rows % 2 == 1 ? kernel.rows / 2 : kernel.rows / 2 - 1;
    int right = kernel.cols % 2 == 1 ? kernel.cols / 2 : kernel.cols / 2 - 1;
    cv::copyMakeBorder(bin, binPadded, top, bottom, left, right, cv::BORDER_REFLECT101);
    cv::Mat myHitOrMiss = my_hit_or_miss(binPadded, kernel);
    std::cout << "myHitOrMiss = \n" << myHitOrMiss << std::endl << std::endl;
    
    cv::Mat hitOrMissDiff = myHitOrMiss - hitOrMiss;
    std::cout << "hitOrMissDiff = \n" << hitOrMissDiff << std::endl << std::endl;
    */
    /*
    cv::Mat img = cv::imread("/home/xlll/Downloads/opencv/samples/data/notes.png", cv::IMREAD_GRAYSCALE);
    if (img.empty()) {
        std::cout << "Failed to read image!" << std::endl;
        return EXIT_FAILURE;
    }
    cv::imshow("note", img);
    
    cv::Mat bin;
    cv::bitwise_not(img, img);
    cv::threshold(img, bin, 100, 255, cv::THRESH_BINARY | cv::THRESH_OTSU);
    cv::imshow("bin", bin);
    
    cv::Mat horizontalStru = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(bin.cols / 20, 1));
    cv::Mat horizontal;
    cv::morphologyEx(bin, horizontal, cv::MORPH_OPEN, horizontalStru);
    cv::imshow("horizontal", ~horizontal);
    
    cv::Mat verticalStru = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(1, bin.rows / 30));
    cv::Mat vertical;
    cv::morphologyEx(bin, vertical, cv::MORPH_OPEN, verticalStru);
    cv::imshow("vertical", ~vertical);
    */
    
    cv::Mat ellipse = cv::imread("/home/xlll/Downloads/opencv/samples/data/ellipses.jpg", cv::IMREAD_GRAYSCALE);
    cv::imshow("ellipse", ellipse);
    cv::Mat ellipseBin;
    cv::threshold(ellipse, ellipseBin, 50, 255, cv::THRESH_BINARY);
    cv::imshow("ellipseBin", ellipseBin);
    cv::Mat ellipseClose;
    cv::Mat closeStru = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(21, 21), cv::Point(-1, -1));
    cv::morphologyEx(ellipseBin, ellipseClose, cv::MORPH_CLOSE, closeStru, cv::Point(-1, -1), 1, cv::BORDER_REFLECT101);
    cv::imshow("ellipseClose", ellipseClose);
    cv::Mat ellipseGradient;
    cv::Mat gradientStru = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(5, 5), cv::Point(-1, -1));
    cv::morphologyEx(ellipseClose, ellipseGradient, cv::MORPH_GRADIENT, gradientStru, cv::Point(-1, -1), 1, cv::BORDER_REFLECT101);
    cv::imshow("ellipseGradient", ellipseGradient);
    
    cv::waitKey(0);
    
    return EXIT_SUCCESS;
}
